import random
import time
from functools import reduce

class Position(object):
    """Basic Position class which just contains x and y information."""
    def __init__(self, x_, y_):
        self.x = x_
        self.y = y_
    
    def next(self, position_bias):
        return Position(self.x + position_bias[0], self.y + position_bias[1])

    def __str__(self):
        return '(' + str(self.x) + ',' + str(self.y) + ')'

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash(self.x * 1000 + self.y)


class DirectedPosition(object):
    """Position class which contains position's parent information."""
    def __init__(self, pos_, parent_pos_):
        self.pos = pos_
        self.parent_pos = parent_pos_

    def next(self, position_bias):
        return DirectedPosition(Position(self.pos.x + position_bias[0], self.pos.y + position_bias[1]), self.pos)
    
    def __str__(self):
        return '[Point: (' + str(self.pos.x) + ',' + str(self.pos.y) + '), Parent Point: (' + str(self.parent_pos.x) + ',' + str(self.parent_pos.y) + ')]'

    def __eq__(self, other):
        return self.pos.x == other.pos.x and self.pos.y == other.pos.y
    
    def __hash__(self):
        return hash(self.pos.x * 1000 + self.pos.y)


class Snake(object):
    """Main class for snakeAI"""
    def __init__(self, rows_, cols_):
        # some parameters for the snake-map
        self.rows = rows_
        self.cols = cols_

        # some parameters for the snake
        self.snake_body = self.__generateSnake()
        # direction dict: right, down, left, up
        self.directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
        # store cur direction
        self.__cur_direction = 0
        # status list of the snake
        self.__status_list = ['alived', 'dead', 'paused']
        # store the status of cur_snake
        self.__cur_status = 0

        # some paramters for the who game.
        # store position of food
        self.food_position = self.__generateFood()
        # store score
        self.score = 0
        # store max score
        self.max_score = 0
        self.score_list = []
        # store try times
        self.epoch = 1
    
    def __str__(self):
        return reduce(lambda x, y: str(x) + ',' + str(y), self.snake_body)
    
    def __generateSnake(self, snake_length=4):
        """Generate the initial snake with body length of 3"""
        seed_x_min = 1 * self.rows // 3
        seed_x_max = 2 * self.rows // 3
        seed_y_min = 1 * self.cols // 3
        seed_y_max = 2 * self.cols // 3
        snake_body = []
        snake_head = Position(random.randint(seed_x_min, seed_x_max), random.randint(seed_y_min, seed_y_max))
        snake_body.append(snake_head)
        for i in range(1, snake_length - 1):
            if snake_head.y - i >= 0:
                snake_body.append(Position(snake_head.x, snake_head.y - i))
        return snake_body

    def __generateFood(self):
        """Generate food position, note that the food can not appear on the snake's body"""
        food_position = Position(random.randint(1, self.rows - 1), random.randint(1, self.cols - 1))
        while food_position in self.snake_body:
            food_position = Position(random.randint(1, self.rows - 1), random.randint(1, self.cols - 1))
        return food_position

    def getCurrentStatus(self):
        """Getter of current status"""
        return self.__status_list[self.__cur_status]
    
    def setCurrentStatus(self, status):
        """Setter of current status"""
        if status not in self.__status_list:
            return
        self.__cur_status = self.__status_list.index(status)

    def setCurrentDirection(self, new_direction):
        """Setter of current direction"""
        self.__cur_direction = new_direction
    
    def getCurrentDirection(self):
        """Getter of current direction"""
        return self.__cur_direction

    def checkAlived(self, next_head_position):
        """Check whether the snake still alived with its head in next_head_position"""
        
        # Check whether head hit the ceil/floor.
        if next_head_position.x < 0 or next_head_position.x == self.rows:
            return False
        # Check whether head hit the left/right site of wall.
        if next_head_position.y < 0 or next_head_position.y == self.cols:
            return False
        # Check whether head hit its body
        if next_head_position in self.snake_body:
            return False
        return True

    def forward(self, simulation=False):
        """This is the main logit loop for updating the position of the snake"""
        curr_head_position = self.snake_body[0]
        next_head_position = curr_head_position.next(self.directions[self.__cur_direction])

        # If the snake will dead.
        if not self.checkAlived(next_head_position) and simulation == False:
            self.__cur_status = 1
            time.sleep(10)
            self.refresh()
        # If the snake reach the food position.
        elif self.food_position == next_head_position and simulation == False:
            self.snake_body.insert(0, next_head_position)
            self.food_position = self.__generateFood()
            # self.score += 5
            # self.score += len(self.snake_body) // 10
            self.score += 1
        # Otherwise update the position of the snake.
        else:
            self.snake_body.pop()
            self.snake_body.insert(0, next_head_position)
    
    def refresh(self):
        """Refresh snake and food"""
        self.snake_body = self.__generateSnake()
        self.food_position = self.__generateFood()
        self.epoch += 1
        self.score_list.append(self.score)
        self.max_score = max(self.score_list)
        print(self.score_list)
        self.score = 0
        self.__cur_status = 0


if __name__ == '__main__':
    snake = Snake(20, 20)
    
    while(True):
        print(Snake)
        Snake.forward()
        time.sleep(1)